#include <zenoui/Widget.hpp>
#include <zenoui/Event.hpp>
#include <zenoui/Property.hpp>
#include <zenoui/Shape.hpp>
#include <zenoui/check_gl.hpp>
#include <zenox/glm.hpp>
#include <algorithm>
#include <ranges>
#include <deque>

namespace zenoui {

namespace {

struct RecursionLevelGuard {
    int &level;
    ZENO_FORCEINLINE explicit RecursionLevelGuard(int &level) : level(level) {
        level++;
    }
    RecursionLevelGuard(RecursionLevelGuard &&) = delete;
    ZENO_FORCEINLINE ~RecursionLevelGuard() {
        level--;
    }
};

struct OpenGLTransformGuard {
    bool m_transformed;
    ZENO_FORCEINLINE explicit OpenGLTransformGuard(std::optional<Transform> const &transform) {
        m_transformed = transform.has_value();
        if (m_transformed) {
            CHECK_GL(glPushMatrix());
            CHECK_GL(glTranslatef(transform->translation.x, transform->translation.y, 0.0f));
            CHECK_GL(glScalef(transform->scaling, transform->scaling, 1.0f));
        }
    }
    OpenGLTransformGuard(OpenGLTransformGuard &&) = delete;
    ZENO_FORCEINLINE ~OpenGLTransformGuard() {
        if (m_transformed) {
            CHECK_GL(glPopMatrix());
        }
    }
};

struct EventTransformGuard {
    Event *event;
    Transform old_transform;
    ZENO_FORCEINLINE explicit EventTransformGuard(Event *event) : event(event), old_transform(event->transform()) {
    }
    EventTransformGuard(EventTransformGuard &&) = delete;
    ZENO_FORCEINLINE ~EventTransformGuard() {
        event->transform() = old_transform;
    }
};

}

std::optional<Transform> Widget::local_transform() const {
    if (m_shape.layout == AbsoluteLayout) {
        return m_shape.transform.deepcopy().post_translate(rect().topleft());
    }
    return std::nullopt;
}

/* static std::deque<Widget *> Widget::parent_resolution_list() const { */
/*     std::deque<Widget const *> mro; */
/*     for (Widget const *current = this; current; current = current->parent()) { */
/*         mro.push_front(const_cast<Widget *>(current)); */
/*     } */
/*     return mro; */
/* } */

Transform Widget::global_transform() const {
    Transform t{};
    for (Widget const *current = parent(); current; current = current->parent()) {
        if (auto maybe_lt = current->local_transform()) {
            t.post_merge(maybe_lt->inverse());
        }
    }
    t.inverse();
    return t;
}

ZENO_HOT_FN void Widget::do_direct_handle(Event *event) {
    EventTransformGuard _(event);
    event->transform().pre_merge(global_transform());
    event->send_to(this);
}

Widget *Widget::child_at(glm::vec2 const &pos) const {
    for (auto const &ch: m_children | std::views::reverse) {
        if (ch->rect().contains(pos)) {
            return ch.get();
        }
    }
    return nullptr;
}

ZENO_HOT_FN void Widget::do_handle(Event *event) {
    {
        EventTransformGuard _(event);
        if (auto maybe_lt = local_transform())
            event->transform().pre_merge(*maybe_lt);

        auto event_pos = event->pos();
        for (auto const &ch: m_children | std::views::reverse) {
            ZENO_ASSERT(ch);
            if (ch->event_rect().contains(event_pos)) {
                ch->do_handle(event);
                if (event->accepted()) {
                    return;
                }
            }
        }
    }
    if (rect().contains(event->pos())) [[likely]]
        event->send_to(this);
}

ZENO_HOT_FN void Widget::do_paint(Rect const &parent_rect, Transform const &transform, int const *viewdims) {
    Rect myrect = transform.rect_to(rect());
    switch (m_shape.scissor) {
    case ScissorParentOnly:
        myrect = parent_rect;
        break;
    case ScissorBoth:
        myrect = myrect.intersectWith(parent_rect);
        break;
    default:
        break;
    };
    if (myrect.empty()) return;
    CHECK_GL(glScissor(viewdims[0] + (int)myrect.left(), viewdims[1] + viewdims[3] - (int)myrect.bottom(),
                       (int)myrect.width(), (int)myrect.height()));
    paint();
    auto maybe_lt = local_transform();
    OpenGLTransformGuard _(maybe_lt);
    Transform transform_ch = transform;
    if (maybe_lt) {
        transform_ch.pre_merge(*maybe_lt);
    }
    for (auto const &child: m_children) {
        child->do_paint(myrect, transform_ch, viewdims);
    }
}

ZENO_COLD_FN void Widget::paint_debug_rect() {
    static int recursion_levels = 0;
    RecursionLevelGuard _(recursion_levels);

    auto [strength, stipple] = [] {
        const double phase_interval = 8.0;
        const double shift_interval = 0.03;
        double time = glfwGetTime();
        double phase = glm::sin((time / phase_interval - recursion_levels * 0.3) * glm::pi<double>() * 2.0);
        auto shift = (unsigned int)std::fmod(glm::floor(time / shift_interval), 16.0) % 16;
        auto stipple = static_cast<unsigned short>((0b111111111110000111111111110000u >> shift) & 0xffff);
        return std::make_pair((float)std::max(0.0, (phase - 0.5) * 2.0), stipple);
    }();

    static const glm::vec3 rainbow_colors[] = {
        glm::vec3(1.0f, 0.0f, 0.0f),   // red
        glm::vec3(1.0f, 0.5f, 0.0f),   // orange
        glm::vec3(1.0f, 1.0f, 0.0f),   // yellow
        glm::vec3(0.0f, 1.0f, 0.0f),   // green
        glm::vec3(0.0f, 0.0f, 1.0f),   // blue
        glm::vec3(0.29f, 0.0f, 0.509f), // indigo
        glm::vec3(0.58f, 0.0f, 0.827f) // violet
    };

    if (strength > 0.0f) {
        auto color = rainbow_colors[(recursion_levels - 1) % 7];
        auto width = 2.0f;
        auto myrect = m_rect;
        CHECK_GL(glPushAttrib(GL_ALL_ATTRIB_BITS));
        myrect.extendMargin(-width * 0.5f);
        CHECK_GL(glEnable(GL_LINE_STIPPLE));
        CHECK_GL(glLineWidth(width));
        CHECK_GL(glLineStipple(1.0f, stipple));
        glBegin(GL_LINE_STRIP);
        glColor4fv(glm::value_ptr(glm::vec4(color, strength)));
        glVertex2f(myrect.m_topleft.x, myrect.m_topleft.y);
        glVertex2f(myrect.m_topleft.x + myrect.m_size.x, myrect.m_topleft.y);
        glVertex2f(myrect.m_topleft.x + myrect.m_size.x, myrect.m_topleft.y + myrect.m_size.y);
        glVertex2f(myrect.m_topleft.x, myrect.m_topleft.y + myrect.m_size.y);
        glVertex2f(myrect.m_topleft.x, myrect.m_topleft.y);
        CHECK_GL(glEnd());
        CHECK_GL(glPopAttrib());
    }

    {
        OpenGLTransformGuard _(local_transform());
        for (auto const &child: m_children) {
            child->paint_debug_rect();
        }
    }
}

ZENO_COLD_FN Rect Widget::global_rect() const {
    return global_transform().rect_to(rect());
}

ZENO_HOT_FN Rect Widget::inner_rect() const {
    auto myrect = m_rect;
    auto l = m_shape.paddings[0][0].eval(myrect.m_size[0]);
    auto r = m_shape.paddings[0][1].eval(myrect.m_size[0]);
    auto t = m_shape.paddings[1][0].eval(myrect.m_size[1]);
    auto b = m_shape.paddings[1][1].eval(myrect.m_size[1]);
    auto padhs = std::max(0.0f, myrect.m_size[0] - l.x - r.x) / std::max(1.0f, l.y + r.y + 1.0f);
    auto padvs = std::max(0.0f, myrect.m_size[1] - t.x - b.x) / std::max(1.0f, t.y + b.y + 1.0f);
    myrect.extendMargin(l.x + l.y * padhs, r.x + r.y * padhs, t.x + t.y * padvs, b.x + b.y * padvs, -1.0f);
    return myrect;
}

ZENO_HOT_FN void Widget::update_rect(Widget *child) { // TODO: maybe we'd sep abs layout to view specialized widget?
    if (m_children.empty())
        return;
    size_t i = 0;
    size_t axis = 0;
    switch (m_shape.layout) {
    case HorizontalLayout:
        axis = 0;
        break;
    case VerticalLayout:
        axis = 1;
        break;
    default:
        {
            auto myrect = m_rect;
            if (child) {
                Rect chrect{};
                for (int k = 0; k < 2; k++) {
                    auto ems = child->m_shape.eval_middle_size(k, myrect);
                    chrect.m_size[k] = ems.x + ems.y * myrect.m_size[k];
                }
                chrect.m_topleft = child->m_shape.position;
                child->update_rect(chrect);
            } else {
                for (auto const &child: m_children) {
                    Rect chrect{};
                    for (int k = 0; k < 2; k++) {
                        auto ems = child->m_shape.eval_middle_size(k, myrect);
                        chrect.m_size[k] = ems.x + ems.y * myrect.m_size[k];
                    }
                    chrect.m_topleft = child->m_shape.position;
                    child->update_rect(chrect);
                }
            }
        }
        return;
    }
    auto myrect = inner_rect();
    glm::vec2 sum_size(0.0f);
    std::vector<glm::vec2> diff_size;
    for (auto const &child: m_children) {
        auto diff = child->m_shape.eval_middle_size(axis, myrect);
        diff_size.push_back(diff);
        sum_size += diff;
    }
    auto px_per_stretch_fac = sum_size.y > 0.0f ?
        std::max(0.0f, myrect.m_size[axis] - sum_size.x) / sum_size.y : 0.0f;
    float rescanned_size = 0.0f;
    for (auto const &child: m_children) {
        auto subrect = myrect;
        auto subsize = diff_size[i].x + px_per_stretch_fac * diff_size[i].y;
        {
            auto l = child->m_shape.margins[axis][0].eval(subsize);
            auto r = child->m_shape.margins[axis][1].eval(subsize);
            auto marhs = std::max(0.0f, subsize - l.x - r.x) / std::max(1.0f, l.y + r.y + 1.0f);
            auto marl = l.x + l.y * marhs;
            auto marr = r.x + r.y * marhs;
            subrect.m_size[axis] = std::max(0.0f, subsize - marl - marr);
            subrect.m_topleft[axis] += rescanned_size + marl;
        }
        {
            auto hisize = myrect.m_size[1 - axis];
            auto top = child->m_shape.margins[1 - axis][0].eval(hisize);
            auto bot = child->m_shape.margins[1 - axis][1].eval(hisize);
            auto marvs = std::max(0.0f, hisize - top.x - bot.x) / std::max(1.0f, top.y + bot.y + 1.0f);
            auto martop = top.x + top.y * marvs;
            auto marbot = bot.x + bot.y * marvs;
            subrect.m_size[1 - axis] = std::max(0.0f, subrect.m_size[1 - axis] - martop - marbot);
            subrect.m_topleft[1 - axis] += martop;
        }
        child->update_rect(subrect);
        rescanned_size += subsize;
        i++;
    }
}

void Widget::batched_add_child(std::vector<zenox::shared_ptr<Widget>> children) {
    for (auto &child: children) {
        Widget *child_ptr = child.get();
        child_ptr->m_parent = this;
        auto pos = std::upper_bound(m_children.begin(), m_children.end(), child_ptr->m_shape.zvalue,
                                    [] (float newz, auto const &oldch) { return newz < oldch->m_shape.zvalue; });
        m_children.insert(pos, std::move(child));
    }
    update_rect();
}

std::vector<Widget *> Widget::children_list() const {
    std::vector<Widget *> ret;
    ret.reserve(m_children.size());
    for (auto const &ch: m_children) {
        ret.push_back(ch.get());
    }
    return ret;
}

void Widget::remove_child(Widget *child) {
    auto pos = std::find_if(m_children.begin(), m_children.end(), [child] (auto &&elm) {
        return elm.get() == child;
    });
    if (pos == m_children.end()) [[unlikely]]
        zenox::fatal_error("cannot remove child whose pointer is not inside m_children at all [{}]", (void *)child);
    m_children.erase(pos);
}

Widget *Widget::add_child(zenox::shared_ptr<Widget> child) {
    Widget *child_ptr = child.get();
    child_ptr->m_parent = this;
    auto pos = std::upper_bound(m_children.begin(), m_children.end(), child_ptr->m_shape.zvalue,
                                [] (float newz, auto const &oldch) { return newz < oldch->m_shape.zvalue; });
    m_children.insert(pos, std::move(child));
    update_rect();
    return child_ptr;
}

#define _FUNCTION(_EventType) \
    void Widget::handle(_EventType *) { \
    }
ZENO_FOREACH_EVENT_TYPE(_FUNCTION) // generate corresponding implementations here
#undef _FUNCTION

#define _FUNCTION(_PropType) \
    void Widget::property(_PropType *prop) { \
        (void)prop; \
    }
ZENO_FOREACH_PROPERTY_TYPE(_FUNCTION) // generate corresponding implementations here
#undef _FUNCTION

Widget::Widget() = default;
Widget::~Widget() = default;

}
